import java.io.IOException;

public class ConstructorArbolesTiny {

	private Token tact;

	private AnalizadorLexico analizadorLexico;

	private TinyGA gramaticaAtributos = new TinyGA();

	public ConstructorArbolesTiny(AnalizadorLexico analizadorLexico) {
		this.analizadorLexico = analizadorLexico;
	}

	public TinyGA.Programa parse() throws IOException {
		tact = analizadorLexico.sigToken();
		TinyGA.Programa p = recPrograma();
		rec(CatLexica.EOF);
		return p;
	}

	private TinyGA.Programa recPrograma() throws IOException {
		if (tokenActual(CatLexica.INT) || tokenActual(CatLexica.BOOLEAN)) {
			TinyGA.Decs aDecs = recDecs();
			if (tokenActual(CatLexica.AMPER)) {
				rec(CatLexica.AMPER);
				TinyGA.Insts aInsts = recInsts();
				return progR1(aDecs, aInsts);
			}
		} else if (tokenActual(CatLexica.IDEN) || tokenActual(CatLexica.IF) || tokenActual(CatLexica.DO)) {
					TinyGA.Insts aInsts = recInsts();
					return progR2(aInsts);
		}
		errorSintactico();
		return null;
	}

	private TinyGA.Decs recDecs() throws IOException {
		TinyGA.Dec aDec = recDec();
		TinyGA.Decs aRDecs = recRDecs(decsR2(aDec));
		return aRDecs;
	}

	private TinyGA.Decs recRDecs(TinyGA.Decs ahRDecs0)
			throws IOException {
		if (tokenActual(CatLexica.PUNTOCOMA)) {
			rec(CatLexica.PUNTOCOMA);
			TinyGA.Dec aDec = recDec();
			TinyGA.Decs aRDecs1 = recRDecs(decsR1(ahRDecs0, aDec));
			return aRDecs1;
		} else {
			return ahRDecs0;
		}
	}

	private TinyGA.Dec recDec() throws IOException {
		TinyGA.Tipo aTipo = recTipo();
		if (tokenActual(CatLexica.IDEN)) {
			Token tIden = tact;
			rec(CatLexica.IDEN);
			return decR1(aTipo, tIden);
		} else {
			errorSintactico(CatLexica.IDEN);
			return null;
		}
	}

	private TinyGA.Tipo recTipo() throws IOException {
		if (tokenActual(CatLexica.INT)) {
			Token tInt = tact;
			rec(CatLexica.INT);
			return tipoR1(tInt);
		} else if (tokenActual(CatLexica.BOOLEAN)) {
			Token tBool = tact;
			rec(CatLexica.BOOLEAN);
			return tipoR2(tBool);
		} else {
			errorSintactico();
			return null;
		}

	}

	private TinyGA.Insts recInsts() throws IOException {
		TinyGA.Inst aInst = recInst();
		TinyGA.Insts aRInsts = recRInsts(instsR2(aInst));
		return aRInsts;
	}

	private TinyGA.Insts recRInsts(TinyGA.Insts ahRInsts0)
			throws IOException {
		if (tokenActual(CatLexica.PUNTOCOMA)) {
			rec(CatLexica.PUNTOCOMA);
			TinyGA.Inst aInst = recInst();
			TinyGA.Insts aRInsts = recRInsts(instsR1(ahRInsts0,
					aInst));
			return aRInsts;
		} else {
			return ahRInsts0;
		}

	}

	private TinyGA.Inst recInst() throws IOException {
		if (tokenActual(CatLexica.IDEN)) {
			return recIAsig();
		} else if (tokenActual(CatLexica.IF)) {
			return recIIF();
		} else if (tokenActual(CatLexica.DO)) {
			return recIDO();
		} else {
			errorSintactico();
			return null;
		}

	}

	private TinyGA.Inst recIAsig() throws IOException {
		if (tokenActual(CatLexica.IDEN)) {
			Token tIden = tact;
			rec(CatLexica.IDEN);
			if (tokenActual(CatLexica.IGUAL)) {
				rec(CatLexica.IGUAL);
				TinyGA.Exp0 aExpr0 = recExp0();
				return iasigR1(tIden, aExpr0);
			} else {
				errorSintactico(CatLexica.IGUAL);
				return null;
			}
		} else {
			errorSintactico(CatLexica.IDEN);
			return null;
		}
	}

	private TinyGA.Inst recIIF() throws IOException {
		if (tokenActual(CatLexica.IF)) {

			rec(CatLexica.IF);
			TinyGA.Casos aCasos = recCasos();
			if (tokenActual(CatLexica.FI)) {
				rec(CatLexica.FI);
				return iifR1(aCasos);
			} else {
				errorSintactico(CatLexica.FI);
				return null;
			}
		} else {
			errorSintactico(CatLexica.IF);
			return null;
		}
	}

	private TinyGA.Inst recIDO() throws IOException {
		if (tokenActual(CatLexica.DO)) {

			rec(CatLexica.DO);
			TinyGA.Casos aCasos = recCasos();
			if (tokenActual(CatLexica.OD)) {
				rec(CatLexica.OD);
				return idoR1(aCasos);
			} else {
				errorSintactico(CatLexica.OD);
				return null;
			}
		} else {
			errorSintactico(CatLexica.DO);
			return null;
		}
	}

	private TinyGA.Casos recCasos() throws IOException {

		TinyGA.Caso aCaso = recCaso();
		TinyGA.Casos aRCasos = recRCasos(casosR2(aCaso));
		return aRCasos;

	}

	private TinyGA.Casos recRCasos(TinyGA.Casos ahRCasos0)
			throws IOException {
		if (tokenActual(CatLexica.CORCHETES)) {
			rec(CatLexica.CORCHETES);
			TinyGA.Caso aCaso = recCaso();
			TinyGA.Casos aRCasos = recRCasos(casosR1(ahRCasos0, aCaso));
			return aRCasos;
		} else {
			return ahRCasos0;
		}

	}

	private TinyGA.Caso recCaso() throws IOException {
		if (tokenActual(CatLexica.CASE)) {

			rec(CatLexica.CASE);
			TinyGA.Exp0 aExpr0 = recExp0();
			if (tokenActual(CatLexica.FLECHA)) {

				rec(CatLexica.FLECHA);
				TinyGA.Insts aInsts = recInsts();
				return casoR1(aExpr0, aInsts);
			} else {
				errorSintactico(CatLexica.FLECHA);
				return null;
			}
		} else {
			errorSintactico(CatLexica.CASE);
			return null;
		}
	}

	private TinyGA.Exp0 recExp0() throws IOException {
		TinyGA.Exp1 aExp1 = recExp1();
		TinyGA.Exp0 aRExp0 = recRExp0(aExp1);
		return aRExp0;
	}

	private TinyGA.Exp0 recRExp0(TinyGA.Exp1 ahRExp0) throws IOException {
		if (esOpComparacion()) {
			TinyGA.OpComparacion aOpc = recOpComp();
			TinyGA.Exp1 aExp1 = recExp1();
			return exp0R1(ahRExp0, aExp1, aOpc);
		} else {

			return exp0R2(ahRExp0);
		}
	}
	
	private TinyGA.Exp1 recExp1() throws IOException {
		TinyGA.Exp2 aExp2 = recExp2();
		TinyGA.Exp1 aRExp1 = recRExp1(exp1R2(aExp2));
		return aRExp1;
	}

	private TinyGA.Exp1 recRExp1(TinyGA.Exp1 ahExp1_0) throws IOException {

		if (esOpAditivo()) {
			TinyGA.OpAditivo aOpA = recOpAdiv();
			TinyGA.Exp2 aExp2 = recExp2();
			TinyGA.Exp1 aRExp1 = recRExp1(exp1R1(ahExp1_0, aExp2, aOpA));
			return aRExp1;
		} else {
			return ahExp1_0;
		}
	}

	private TinyGA.Exp2 recExp2() throws IOException {
		TinyGA.Exp3 aExp3 = recExp3();
		TinyGA.Exp2 aRExp2 = recRExp2(exp2R2(aExp3));
		return aRExp2;
	}

	private TinyGA.Exp2 recRExp2(TinyGA.Exp2 ahExp2_0) throws IOException {

		if (esOpMultiplicativo()) {
			TinyGA.OpMultiplicativo aOpM = recOpMultipliv();
			TinyGA.Exp3 aExp3 = recExp3();
			TinyGA.Exp2 aRExp2 = recRExp2(exp2R1(ahExp2_0, aExp3, aOpM));
			return aRExp2;
		} else {
			return ahExp2_0;
		}
	}

	private TinyGA.Exp3 recExp3() throws IOException {
		if (esOpUnario()) {
			TinyGA.OpUnario aDeOpU = recOpUnar();
			TinyGA.Exp3 aExp3 = recExp3();
			return exp3R1(aDeOpU, aExp3);
		} else {
			TinyGA.Exp4 aExp4 = recExp4();
			return exp3R2(aExp4);
		}
	}

	private TinyGA.Exp4 recExp4() throws IOException {
		if (tokenActual(CatLexica.TRUE)) {

			rec(CatLexica.TRUE);
			return exp4R1();
		} else if (tokenActual(CatLexica.FALSE)) {

			rec(CatLexica.FALSE);
			return exp4R2();
		}

		else if (tokenActual(CatLexica.NUM)) {
			Token tNum = tact;
			rec(CatLexica.NUM);
			return exp4R3(tNum);
		} else if (tokenActual(CatLexica.IDEN)) {
			Token tIden = tact;
			rec(CatLexica.IDEN);
			return exp4R4(tIden);
		}

		else if (tokenActual(CatLexica.PAP)) {
			rec(CatLexica.PAP);
			TinyGA.Exp0 aExp0 = recExp0();
			rec(CatLexica.PCIERRE);
			return exp4R5(aExp0);
		} else
			errorSintactico();
		return null;
	}

	private boolean esOpUnario() {
		return tokenActual(CatLexica.MENOS) || tokenActual(CatLexica.NOT);

	}

	private boolean esOpMultiplicativo() {
		return tokenActual(CatLexica.MULT) || tokenActual(CatLexica.AND)
				|| tokenActual(CatLexica.DIV);

	}

	private boolean esOpComparacion() {
		return tokenActual(CatLexica.EQ) || tokenActual(CatLexica.NEQ)
				|| tokenActual(CatLexica.GT) || tokenActual(CatLexica.GE)
				|| tokenActual(CatLexica.LT) || tokenActual(CatLexica.LE);

	}

	private boolean esOpAditivo() {
		return tokenActual(CatLexica.MAS) || tokenActual(CatLexica.MENOS)
				|| tokenActual(CatLexica.OR);

	}

	private TinyGA.OpComparacion recOpComp() throws IOException {
		if (tokenActual(CatLexica.EQ)) {
			rec(CatLexica.EQ);
			return opCR1();
		} else if (tokenActual(CatLexica.NEQ)) {
			rec(CatLexica.NEQ);
			return opCR2();
		} else if (tokenActual(CatLexica.GT)) {
			rec(CatLexica.GT);
			return opCR3();
		} else if (tokenActual(CatLexica.GE)) {
			rec(CatLexica.GE);
			return opCR4();
		} else if (tokenActual(CatLexica.LT)) {
			rec(CatLexica.LT);
			return opCR5();
		} else if (tokenActual(CatLexica.LE)) {
			rec(CatLexica.LE);
			return opCR6();
		}

		else {
			errorSintactico(CatLexica.EQ, CatLexica.NEQ, CatLexica.GT,
					CatLexica.GE, CatLexica.LT, CatLexica.LE);
			return null;
		}

	}

	private TinyGA.OpAditivo recOpAdiv() throws IOException {
		if (tokenActual(CatLexica.MAS)) {
			rec(CatLexica.MAS);
			return opAR1();
		} else if (tokenActual(CatLexica.MENOS)) {
			rec(CatLexica.MENOS);
			return opAR2();
		} else if (tokenActual(CatLexica.OR)) {
			rec(CatLexica.OR);
			return opAR3();
		} else {
			errorSintactico(CatLexica.MAS, CatLexica.MENOS, CatLexica.OR);
			return null;
		}

	}

	private TinyGA.OpMultiplicativo recOpMultipliv() throws IOException {
		if (tokenActual(CatLexica.MULT)) {
			rec(CatLexica.MULT);
			return opMul1();
		} else if (tokenActual(CatLexica.DIV)) {
			rec(CatLexica.DIV);
			return opMul2();
		} else if (tokenActual(CatLexica.AND)) {
			rec(CatLexica.AND);
			return opMul3();
		} else {
			errorSintactico(CatLexica.MULT, CatLexica.DIV, CatLexica.AND);
			return null;
		}

	}

	private TinyGA.OpUnario recOpUnar() throws IOException {
		if (tokenActual(CatLexica.MENOS)) {
			rec(CatLexica.MENOS);
			return opUn1();
		} else if (tokenActual(CatLexica.NOT)) {
			rec(CatLexica.NOT);
			return opUn2();
		} else {
			errorSintactico(CatLexica.MENOS, CatLexica.NOT);
			return null;
		}

	}

	private void rec(CatLexica cat) throws IOException {
		if (tokenActual(cat)) {
			tact = sigToken();
		} else {
			errorSintactico(cat);
		}
	}
	
	private Token sigToken() throws IOException {
		return analizadorLexico.sigToken();
	}
	
	private boolean tokenActual(CatLexica... cats) {
		boolean encontrada = false;
		int i = 0;
		while (i < cats.length && !encontrada) {
			encontrada = tact.leeCategoria() == cats[i];
			i++;
		}
		return encontrada;
	}

    private void errorSintactico(CatLexica... catsEsperadas) {
		System.err.print("(" + tact.leeFila() + "," + tact.leeCol() + ")"
				+ "ERROR SINTACTICO: Encontrado " + tact
				+ ". Se esperaba alguno de los siguientes elementos: ");
		for (CatLexica catEsperada : catsEsperadas) {
			System.err.print(catEsperada + " ");
		}
		System.err.println();
		System.exit(1);
	}

    
    //--------------------Funciones que llamaran a la clase TinyGA----------------------//
	private TinyGA.Programa progR1(TinyGA.Decs aDecs, TinyGA.Insts insts) {
		return gramaticaAtributos.new ProgramaR1(aDecs, insts);
	}

	private TinyGA.Programa progR2(TinyGA.Insts insts) {
		return gramaticaAtributos.new ProgramaR2(insts);
	}

	private TinyGA.Decs decsR2(TinyGA.Dec aDec) {
		return gramaticaAtributos.new DecsR2(aDec);
	}

	private TinyGA.Dec decR1(TinyGA.Tipo tipo, Token iden) {
		return gramaticaAtributos.new DecR1(tipo, iden);
	}

	private TinyGA.Decs decsR1(TinyGA.Decs ahDeRDecs_0, TinyGA.Dec aDec) {
		return gramaticaAtributos.new DecsR1(ahDeRDecs_0, aDec);
	}

	private TinyGA.Tipo tipoR1(Token tDeInt) {
		return gramaticaAtributos.new TipoR1(tDeInt);
	}

	private TinyGA.Tipo tipoR2(Token tDeBool) {
		return gramaticaAtributos.new TipoR2(tDeBool);
	}

	private TinyGA.Insts instsR2(TinyGA.Inst aInst) {
		return gramaticaAtributos.new InstsR2(aInst);
	}

	private TinyGA.Insts instsR1(TinyGA.Insts ahDeRInsts_0, TinyGA.Inst aInst) {
		return gramaticaAtributos.new InstsR1(ahDeRInsts_0, aInst);
	}

	private TinyGA.Inst iasigR1(Token tDeIden, TinyGA.Exp0 aExpr0) {
		return gramaticaAtributos.new IAsigR1(tDeIden, aExpr0);
	}

	private TinyGA.Inst iifR1(TinyGA.Casos aCasos) {
		return gramaticaAtributos.new IIfR1(aCasos);
	}

	private TinyGA.Inst idoR1(TinyGA.Casos aCasos) {
		return gramaticaAtributos.new IDoR1(aCasos);
	}

	private TinyGA.Caso casoR1(TinyGA.Exp0 aExpr0, TinyGA.Insts aInsts) {
		return gramaticaAtributos.new CasoR1(aExpr0, aInsts);
	}

	private TinyGA.Casos casosR2(TinyGA.Caso aCaso) {
		return gramaticaAtributos.new CasosR2(aCaso);
	}

	private TinyGA.Casos casosR1(TinyGA.Casos ahDeRCasos_0, TinyGA.Caso aCaso) {
		return gramaticaAtributos.new CasosR1(ahDeRCasos_0, aCaso);
	}

	private TinyGA.Exp0 exp0R1(TinyGA.Exp1 ahRExp0, TinyGA.Exp1 exp1,
			TinyGA.OpComparacion opc) {
		return gramaticaAtributos.new Exp0R1(ahRExp0, exp1, opc);
	}

	private TinyGA.Exp0 exp0R2(TinyGA.Exp1 ahRExp0) {
		return gramaticaAtributos.new Exp0R2(ahRExp0);
	}

	private TinyGA.Exp1 exp1R2(TinyGA.Exp2 aExp2) {
		return gramaticaAtributos.new Exp1R2(aExp2);
	}

	private TinyGA.Exp1 exp1R1(TinyGA.Exp1 ahDeExp1_0, TinyGA.Exp2 aExp2,
			TinyGA.OpAditivo aOpA) {
		return gramaticaAtributos.new Exp1R1(ahDeExp1_0, aExp2, aOpA);
	}

	private TinyGA.Exp2 exp2R2(TinyGA.Exp3 aExp3) {
		return gramaticaAtributos.new Exp2R2(aExp3);
	}

	private TinyGA.Exp2 exp2R1(TinyGA.Exp2 ahDeExp2_0, TinyGA.Exp3 aExp3,
			TinyGA.OpMultiplicativo aOpM) {
		return gramaticaAtributos.new Exp2R1(ahDeExp2_0, aExp3, aOpM);
	}

	private TinyGA.Exp3 exp3R1(TinyGA.OpUnario aDeOpU, TinyGA.Exp3 aDeExp3) {
		return gramaticaAtributos.new Exp3R1(aDeExp3, aDeOpU);
	}

	private TinyGA.Exp3 exp3R2(TinyGA.Exp4 aDeExp4) {
		return gramaticaAtributos.new Exp3R2(aDeExp4);
	}

	private TinyGA.Exp4 exp4R1() {
		return gramaticaAtributos.new Exp4R1();
	}

	private TinyGA.Exp4 exp4R2() {
		return gramaticaAtributos.new Exp4R2();
	}

	private TinyGA.Exp4 exp4R3(Token tNum) {
		return gramaticaAtributos.new Exp4R3(tNum);
	}

	private TinyGA.Exp4 exp4R4(Token tIden) {
		return gramaticaAtributos.new Exp4R4(tIden);
	}

	private TinyGA.Exp4 exp4R5(TinyGA.Exp0 exp0) {
		return gramaticaAtributos.new Exp4R5(exp0);
	}

	private TinyGA.OpComparacion opCR1() {
		return gramaticaAtributos.new OpCompR1();
	}

	private TinyGA.OpComparacion opCR2() {
		return gramaticaAtributos.new OpCompR2();
	}

	private TinyGA.OpComparacion opCR3() {
		return gramaticaAtributos.new OpCompR3();
	}

	private TinyGA.OpComparacion opCR4() {
		return gramaticaAtributos.new OpCompR4();
	}

	private TinyGA.OpComparacion opCR5() {
		return gramaticaAtributos.new OpCompR5();
	}

	private TinyGA.OpComparacion opCR6() {
		return gramaticaAtributos.new OpCompR6();
	}

	private TinyGA.OpAditivo opAR1() {
		return gramaticaAtributos.new OpAdivR1();
	}

	private TinyGA.OpAditivo opAR2() {
		return gramaticaAtributos.new OpAdivR2();
	}

	private TinyGA.OpAditivo opAR3() {
		return gramaticaAtributos.new OpAdivR3();
	}

	private TinyGA.OpMultiplicativo opMul1() {
		return gramaticaAtributos.new OpMultR1();
	}

	private TinyGA.OpMultiplicativo opMul2() {
		return gramaticaAtributos.new OpMultR2();
	}

	private TinyGA.OpMultiplicativo opMul3() {
		return gramaticaAtributos.new OpMultR3();
	}

	private TinyGA.OpUnario opUn1() {
		return gramaticaAtributos.new OpUnarioR1();
	}

	private TinyGA.OpUnario opUn2() {
		return gramaticaAtributos.new OpUnarioR2();
	}
//---------------------------------------------------------------------------------------//
}